
#include "msg/AirFrame_serial.h"
#include "PhyUtils.h"

void RadioStateAnalogueModel::filterSignal(AirFrame *frame, const Coord& sendersPos, const Coord& receiverPos)
{
    Signal& signal = frame->getSignal();

    omnetpp::simtime_t_cref signalStart = frame->getSendingTime() + signal.getPropagationDelay();
    omnetpp::simtime_t_cref signalEnd   = frame->getSendingTime() + signal.getPropagationDelay() + frame->getDuration();

    RSAMMapping* attMapping = new RSAMMapping(this, signalStart, signalEnd);

    signal.addAttenuation(attMapping);
}


void RadioStateAnalogueModel::cleanUpUntil(omnetpp::simtime_t_cref t)
{
    // assert that list is not empty
    assert(!radioStateAttenuation.empty());

    /* the list contains at least one element */

    // CASE: t is smaller or equal the timepoint of the first element ==> nothing to do, return
    if ( t <= radioStateAttenuation.front().getTime() )
    {
        return;
    }

    // CASE: t is greater than the timepoint of the last element
    // ==> clear complete list except the last element, return
    if ( t > radioStateAttenuation.back().getTime() )
    {
        ListEntry lastEntry = radioStateAttenuation.back();
        radioStateAttenuation.clear();
        radioStateAttenuation.push_back(lastEntry);
        return;
    }

    /*
     * preconditions from now on:
     * 1. list contains at least two elements, since 2. + 3.
     * 2. t > first_timepoint
     * 3. t <= last_timepoint
     */

    // get an iterator and set it to the first timepoint >= t
    std::list<ListEntry>::iterator it;
    it = lower_bound(radioStateAttenuation.begin(), radioStateAttenuation.end(), t);


    // CASE: list contains an element with exactly the given key
    if ( it->getTime() == t )
    {
        radioStateAttenuation.erase(radioStateAttenuation.begin(), it);
        return;
    }

    // CASE: t is "in between two elements"
    // ==> set the iterators predecessors time to t, it becomes the first element
    it--; // go back one element, possible since this one has not been the first one

    it->setTime(t); // set this elements time to t
    radioStateAttenuation.erase(radioStateAttenuation.begin(), it); // and erase all previous elements

}


void RadioStateAnalogueModel::writeRecvEntry(omnetpp::simtime_t_cref time, Argument::mapped_type_cref value)
{
    // bugfixed on 08.04.2008
    assert( (radioStateAttenuation.empty()) || (time >= radioStateAttenuation.back().getTime()) );

    radioStateAttenuation.push_back(ListEntry(time, value));

    if (!currentlyTracking)
    {
        cleanUpUntil(time);

        assert(radioStateAttenuation.back().getTime() == time);
    }
}


Radio::Radio(int numRadioStates,
        bool recordStats,
        int initialState,
        Argument::mapped_type_cref minAtt, Argument::mapped_type_cref maxAtt,
        int currentChannel, int nbChannels):
	                        state(initialState), nextState(initialState),
	                        numRadioStates(numRadioStates),
	                        minAtt(minAtt), maxAtt(maxAtt),
	                        rsam(mapStateToAtt(initialState)),
	                        currentChannel(currentChannel), nbChannels(nbChannels)
{
    assert(nbChannels > 0);
    assert(currentChannel > -1);
    assert(currentChannel < nbChannels);

    radioStates.setName("RadioState");
    radioStates.setEnabled(recordStats);
    radioStates.record(initialState);
    radioChannels.setName("RadioChannel");
    radioChannels.setEnabled(recordStats);
    radioChannels.record(currentChannel);

    // allocate memory for one dimension
    swTimes = new omnetpp::simtime_t* [numRadioStates];

    // go through the first dimension and
    for (int i = 0; i < numRadioStates; i++)
    {
        // allocate memory for the second dimension
        swTimes[i] = new omnetpp::simtime_t[numRadioStates];
    }

    // initialize all matrix entries to 0.0
    for (int i = 0; i < numRadioStates; i++)
    {
        for (int j = 0; j < numRadioStates; j++)
        {
            swTimes[i][j] = 0;
        }
    }
}


Radio::~Radio()
{
    // delete all allocated memory for the switching times matrix
    for (int i = 0; i < numRadioStates; i++)
        delete[] swTimes[i];

    delete[] swTimes;
    swTimes = 0;
}


omnetpp::simtime_t Radio::switchTo(int newState, omnetpp::simtime_t_cref now)
{
    // state to switch to must be in a valid range, i.e. 0 <= newState < numRadioStates
    assert(0 <= newState && newState < numRadioStates);

    // state to switch to must not be SWITCHING
    assert(newState != SWITCHING);

    // return error value if newState is the same as the current state
    // if (newState == state) return -1;

    // return error value if Radio is currently switching
    if (state == SWITCHING) return -1;

    /* REGULAR CASE */

    // set the nextState to the newState and the current state to SWITCHING
    nextState = newState;
    int lastState = state;
    state = SWITCHING;
    radioStates.record(state);

    // make entry to RSAM
    makeRSAMEntry(now, state);

    // return matching entry from the switch times matrix
    return swTimes[lastState][nextState];
}


void Radio::setSwitchTime(int from, int to, omnetpp::simtime_t_cref time)
{
    // assert parameters are in valid range
    assert(time >= 0.0);
    assert(0 <= from && from < numRadioStates);
    assert(0 <= to && to < numRadioStates);

    // it shall not be possible to set times to/from SWITCHING
    assert(from != SWITCHING && to != SWITCHING);


    swTimes[from][to] = time;
    return;
}


void Radio::endSwitch(omnetpp::simtime_t_cref now)
{
    // make sure we are currently switching
    assert(state == SWITCHING);

    // set the current state finally to the next state
    state = nextState;
    radioStates.record(state);

    // make entry to RSAM
    makeRSAMEntry(now, state);

    return;
}


RSAMConstMappingIterator::RSAMConstMappingIterator
(const RadioStateAnalogueModel* rsam,
        omnetpp::simtime_t_cref signalStart,
        omnetpp::simtime_t_cref signalEnd) :
        rsam(rsam),
        signalStart(signalStart),
        signalEnd(signalEnd)
{
    assert(rsam);

    assert( !(signalStart < rsam->radioStateAttenuation.front().getTime()) );

    jumpToBegin();
}


void RSAMConstMappingIterator::jumpTo(const Argument& pos)
{
    // extract the time-component from the argument
    omnetpp::simtime_t_cref t = pos.getTime();

    assert( !(rsam->radioStateAttenuation.empty()) &&
            !(t < rsam->radioStateAttenuation.front().getTime()) );

    // current position is already correct
    if( t == position.getTime() )
        return;

    // this automatically goes over all zero time switches
    it = upper_bound(rsam->radioStateAttenuation.begin(), rsam->radioStateAttenuation.end(), t);

    --it;
    position.setTime(t);
    setNextPosition();
}


void RSAMConstMappingIterator::setNextPosition()
{
    if (hasNext()) // iterator it does not stand on last entry and next entry is before signal end
    {
        if(position.getTime() < signalStart) //signal start is our first key entry
        {
            nextPosition.setTime(signalStart);
        }
        else
        {
            CurrList::const_iterator it2 = it;
            it2++;

            assert(it->getTime() <= position.getTime() && position.getTime() < it2->getTime());

            // point in time for the "pre step" of the next real key entry
            omnetpp::simtime_t_cref preTime = MappingUtils::pre(it2->getTime());

            if(position.getTime() == preTime)
            {
                nextPosition.setTime(it2->getTime());
            }
            else
            {
                nextPosition.setTime(preTime);
            }
        }

    }
    else // iterator it stands on last entry or next entry whould be behind signal end
    {
        nextPosition.setTime(position.getTime() + 1);
    }
}


void RSAMConstMappingIterator::iterateTo(const Argument& pos)
{
    // extract the time component from the passed Argument
    omnetpp::simtime_t_cref t = pos.getTime();

    // ERROR CASE: iterating to a position before (time) the beginning of the mapping is forbidden
    assert( !(rsam->radioStateAttenuation.empty()) &&
            !(t < rsam->radioStateAttenuation.front().getTime()) );

    assert( !(t < position.getTime()) );

    // REGULAR CASES:
    // t >= position.getTime();

    // we are already exactly there
    if( t == position.getTime() )
        return;

    // we iterate there going over all zero time switches
    iterateToOverZeroSwitches(t);

    // update current position
    position.setTime(t);
    setNextPosition();

}


bool RSAMConstMappingIterator::inRange() const
{
    omnetpp::simtime_t_cref t             = position.getTime();
    omnetpp::simtime_t_cref lastEntryTime = std::max(rsam->radioStateAttenuation.back().getTime(), signalStart);

    return 	signalStart <= t
            && t <= signalEnd
            && t <= lastEntryTime;
}


bool RSAMConstMappingIterator::hasNext() const
{
    assert( !(rsam->radioStateAttenuation.empty()) );

    CurrList::const_iterator it2 = it;
    if (it2 != rsam->radioStateAttenuation.end())
        it2++;

    return 	position.getTime() < signalStart
            || (it2 != rsam->radioStateAttenuation.end() && it2->getTime() <= signalEnd);
}


void RSAMConstMappingIterator::iterateToOverZeroSwitches(omnetpp::simtime_t_cref t)
{
    if( it != rsam->radioStateAttenuation.end() && !(t < it->getTime()) )
    {
        // and go over (ignore) all zero-time-switches, to the next greater entry (time)
        while( it != rsam->radioStateAttenuation.end() && !(t < it->getTime()) )
            it++;

        // go back one step, here the iterator 'it' is placed right
        it--;
    }
}


RSAMMapping::argument_value_t RSAMMapping::getValue(const Argument& pos) const
{
    // extract the time-component from the argument
    omnetpp::simtime_t_cref t = pos.getTime();

    // assert that t is not before the first timepoint in the RSAM
    // and receiving list is not empty
    assert( !(rsam->radioStateAttenuation.empty()) && !(t < rsam->radioStateAttenuation.front().getTime()) );

    /* receiving list contains at least one entry */

    // set an iterator to the first entry with timepoint > t
    std::list<RadioStateAnalogueModel::ListEntry>::const_iterator it;
    it = upper_bound(rsam->radioStateAttenuation.begin(), rsam->radioStateAttenuation.end(), t);

    // REGULAR CASE: it points to an element that has a predecessor
    it--; // go back one entry, this one is significant!

    return it->getValue();
}


ConstMappingIterator* RSAMMapping::createConstIterator(const Argument& pos) const
{
    RSAMConstMappingIterator* rsamCMI = new RSAMConstMappingIterator(rsam, signalStart, signalEnd);

    rsamCMI->jumpTo(pos);

    return rsamCMI;
}
